package nl.saxion.security.synchronize;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;

import javax.crypto.SealedObject;

import nl.saxion.security.encryption.TransformerAES;
import nl.saxion.security.transfer.Server;

/**
 * Class for ServerSynchronize
 *
 * @author EII7SCb
 *
 */
public class ServerSynchronize extends Synchronize{
	
	private Date androidDate = null;
	private String CONFIGFILE = "FileList";

	/**
	 * Synchronize for server side
	 * @param directory
	 * @param sealedKey
	 * @param pass
	 */
	public ServerSynchronize(String directory, SealedObject sealedKey, byte[] pass)
	{
		super(new Server(2004,2005), directory, sealedKey, pass);
		fileList =  getFileList();
	}
	/**
	 * returns all the files from the given directory {@link Synchronize} 
	 * @return
	 */
	protected ArrayList<FileIncHash> getLocalFilesIncHash(){
		
		ArrayList<FileIncHash> returnFiles = new ArrayList<FileIncHash>();
		
		for(File file :  getLocalFiles()){
			try {
				FileIncHash fileIncHash = new FileIncHash(file.getAbsolutePath());
				fileIncHash.setDecryptedHash(transformer.generateHash(fileIncHash.getAbsolutePath()));
				returnFiles.add(fileIncHash);
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		return returnFiles;
	}
	
	
	/**
	 * Get FileList form harddisk
	 * @return FileList
	 */
	@SuppressWarnings("unchecked")
	private ArrayList<FileIncHash> getFileList() {
		
		ArrayList<FileIncHash> list = null;
		if((new File(CONFIGFILE).exists()))
		{

			ObjectInputStream inputStream = null;

	        try {
	            inputStream = new ObjectInputStream(new FileInputStream(CONFIGFILE));
	            list = (ArrayList<FileIncHash>)inputStream.readObject()  ;
	        }  catch (Exception e)  {
				e.printStackTrace();
			} finally {
	            try {
	                if (inputStream != null) {
	                    inputStream.close();
	                }
	            } catch (IOException ex) {
	                ex.printStackTrace();
	            }
	        }
		}
		else
			list = new ArrayList<FileIncHash>();
        
        return list;
	}
	
	/**
	 * Save the fileList to harddisk
	 */
	private void saveFileList() {
		
		 ObjectOutputStream outputStream = null;
	     try {
	        outputStream = new ObjectOutputStream(new FileOutputStream(CONFIGFILE));
	        outputStream.writeObject(fileList);
	        } catch (FileNotFoundException e) {
	            e.printStackTrace(); 
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	     	finally {
	        //Close the ObjectOutputStream
	        try {
	            if (outputStream != null) {
	                outputStream.flush();
	                outputStream.close();
	            }
	        } catch (IOException ex) {
	            ex.printStackTrace();
	        }
	    }
	}

	/**
	 * Start synchronizing
	 */
	public void startSynchronize() throws Exception
	{
		sendCodeEncrypt(Code.SYNC);
		waitConfirmEncrypt(Code.CONFIRM);
		
		fileList =  getFileList(); 
		
		sendCodeEncrypt(Code.GETTIME);
		
		recieveTime();

		sendCodeEncrypt(Code.GETFILEPROPERTIES);

		@SuppressWarnings("unchecked")

		ArrayList<FileIncHash> androidFiles = (ArrayList<FileIncHash>) transformer.decryptObject(getKey(), (SealedObject) transfer.getObject());

		sendCodeEncrypt(Code.FILESPROPERTIES_RECIEVED);
		waitConfirmEncrypt(Code.CONFIRM);
		
		//compare Files
		syncFiles(androidFiles);

		System.out.println("Files to android:" + toSendFiles.size());
		if(!toSendFiles.isEmpty()){
			
			
			//send files
			sendCodeEncrypt(Code.SENDFILES);
			waitConfirmEncrypt(Code.CONFIRM);
	
			//encrypt files to send
			ArrayList<FileIncHash> localFilesToSend = encryptFiles(toSendFiles);
			
			//Put in sealedobject			
			transfer.sendObject(transformer.encryptObject(getKey(), toSendFiles));
			waitConfirmEncrypt(Code.READY_FOR_SENDING);
			transfer.dataCon.close();
			
			sendFiles(localFilesToSend);
		
			//remove temp files.
			removeTempFiles(localFilesToSend);
		}
		
		//get files
		System.out.println("get Files form android:" + toReceiveFiles.size());
		
		if(!toReceiveFiles.isEmpty()){
			sendCodeEncrypt(Code.GETFILES);
			waitConfirmEncrypt(Code.CONFIRM);
			
			//Put in sealedobject		
			transfer.sendObject(transformer.encryptObject2(getKey(), toReceiveFiles));
			
			waitConfirmEncrypt(Code.READY_FOR_SENDING);
			transfer.dataCon.close();
			receiveFiles();
			waitConfirmEncrypt(Code.FILERECEIVED);
		}
		
		clearSync();

		this.fileList = getLocalFilesIncHash();
		saveFileList();
		sendCodeEncrypt(Code.SYNC_DONE);
		busySync = false;
		
		System.out.println("Sync klaar!");
	}
	
	/**
	 * Synchronize key used for encryption with other device
	 */
	protected void syncKey() throws Exception
	{
		waitConfirm(Code.HELLO); //kaal sturen
		System.out.println("hello ontvangen!");
		System.out.println("datacon open");
		sendCode(Code.CONFIRM);
		System.out.println("confirm send");
		SealedObject sKey = (SealedObject) transfer.getObject();
		System.out.println("sealedKey ontvangen!");
		//if(sealedKey == null)
			sealedKey = sKey;
			//System.out.println("SEALED:"+sealedKey);
			//System.out.println("ClEAN:"+ this.transformer.decryptObject(password,sealedKey));

		sendCodeEncrypt(Code.CONFIRM);	//encypted
		
		waitConfirmEncrypt(Code.CONFIRM); //klaar
		System.out.println("Key sync done!");
		
	}

	/**
	 * remove temp files from {@link encryptFiles}
	 * @param localFilesToSend
	 */
	private void removeTempFiles(ArrayList<FileIncHash> localFilesToSend) 
	{

		if(localFilesToSend.size() >0){
			FileIncHash file = localFilesToSend.get(0);
			String absolutePath = file.getAbsolutePath();
			String dir =  absolutePath.substring(0,absolutePath.lastIndexOf(File.separator))+ File.separator;
			File tempDir = new File(dir);
			tempDir.delete();
		}
	}

	/**
	 * Receive files form other device according to the set 'toReceiveFiles'
	 */
	protected void receiveFiles() throws Exception
	{
		String templocation = directory.getPath()+ "/temp/encrypted/";
		File dir = new File(templocation);
		if(!dir.exists())
			dir.mkdirs();
			
		for (FileIncHash file : toReceiveFiles)
		{
			String location = directory.getPath()+ "/" + file.getName();
			if(!file.isDelete()){
				sendCodeEncrypt(Code.CONFIRM);
				transfer.getFile(templocation+ file.getName());
				sendCodeEncrypt(Code.FILERECEIVED);
				System.out.println("received Encrypted File: "+ file.getName());
				transformer.decryptFile(getKey(),templocation + file.getName(), location);
				System.out.println("Decrypt File: "+ file.getName());

				new File(templocation+ file.getName()).delete(); //remove temp
			}
			else{
				File localfile = new File(location);
				//remove file from list
				for(FileIncHash delFile : fileList){
					if(delFile.getName().equals(localfile.getName()))
						fileList.remove(delFile);
				}
				localfile.delete();
			}
				
		}
	}
	
	/**
	 * Send Files to other device over dataconnection.
	 * @param localFilesToSend
	 * @throws Exception
	 */
	protected void sendFiles(ArrayList<FileIncHash> localFilesToSend) throws Exception
	{
		for (FileIncHash file : localFilesToSend)
		{	
			if(!file.isDelete()){
				sendCodeEncrypt(Code.CONFIRM);
				transfer.sendFile(file);
				waitConfirmEncrypt(Code.FILERECEIVED);
				System.out.println("File send: "+ file.getName());
				transfer.dataCon.close();	
			}
		}
	}

	/**
	 * Clear list used for synchronize
	 */
	private void clearSync() 
	{
		toReceiveFiles.clear();
		toSendFiles.clear();
	}

	/**
	 *  Get device time from other device
	 * @throws Exception
	 */
	private void recieveTime() throws Exception
	{
		androidDate = (Date) transformer.decryptObject(getKey(), (SealedObject) transfer.getObject());
		sendCodeEncrypt(Code.TIME_RECIEVED);
		waitConfirmEncrypt(Code.CONFIRM);
	}
		
	/**
	 * Compare files to determine witch files to send, recieve and delete.
	 * @param AndroidFiles file list from android
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	private void syncFiles(ArrayList<FileIncHash> AndroidFiles) throws NoSuchAlgorithmException, IOException 
	{
		long tijdPC 	= new Date().getTime();
		long tijdAnd 	= androidDate.getTime();
		long verschil = tijdPC - tijdAnd;

		toReceiveFiles.clear();
		toSendFiles.clear();
		ArrayList<FileIncHash> localFiles = getLocalFilesIncHash();
		ArrayList<FileIncHash> delFiles = checkForDels(localFiles);

		fileList.removeAll(delFiles);
		localFiles.addAll(delFiles);

		
		Iterator<FileIncHash> itPC = localFiles.iterator();
		
        while(itPC.hasNext()){
        	FileIncHash filePC = itPC.next();
        	
        	Iterator<FileIncHash> itA = AndroidFiles.iterator();
        	while(itA.hasNext()){
        		FileIncHash fileA = itA.next();
        	
				if( fileA.getName().equals(filePC.getName()) ) {

					if(fileA.isDelete())
						filePC.delete();
					else if(filePC.isDelete())
						toSendFiles.add(filePC);
					else if(!fileA.getDecryptedHash().equals(filePC.getDecryptedHash()))
					{												
						long tijdverschil = filePC.lastModified() - (fileA.getLastModiDate() + verschil);
						if(tijdverschil<0){
							fileA.setLastModified(fileA.getLastModiDate() + tijdverschil); 
							toReceiveFiles.add(fileA);
						}
						else {
							filePC.setLastModified(filePC.getLastModiDate() + tijdverschil);
							toSendFiles.add(filePC);
						}
					}
						itA.remove();
						itPC.remove();			
						break;
				}
			}
        	
        }
        	toSendFiles.addAll(localFiles);
        	toReceiveFiles.addAll(AndroidFiles);
	}
	
	/**
	 * Encrypt files with key
	 * @param files
	 * @return
	 * @throws Exception
	 */
	private ArrayList<FileIncHash> encryptFiles(ArrayList<FileIncHash> files) throws Exception {
		
		ArrayList<FileIncHash> temp = new ArrayList<FileIncHash>();
		for(FileIncHash file : files)
		{
			if(!file.isDelete())
				temp.add(new FileIncHash(encryptFile(file)));
				
		}
		return temp;
	}
	
	/**
	 * encrypt file to temp directory
	 * @param file
	 * @return pathc of new file
	 * @throws Exception
	 */
	private String encryptFile(File file) throws Exception {
		
		String absolutePath = file.getAbsolutePath();
		String dir =  absolutePath.substring(0,absolutePath.lastIndexOf(File.separator)) +File.separator+"temp"+File.separator+"encrypted"+ File.separator;
		
		File tempDir = new File(dir);
		if(!tempDir.exists())
			tempDir.mkdirs();
		
		String patch = dir + file.getName();
		transformer.encryptFile(getKey(),file.getAbsolutePath(), patch);
		return patch;
	}
}
